package com.benshou.bcss.common.rest;

import com.benshou.bcss.common.j17common.annotation.NotNull;
import com.benshou.bcss.common.property.PropertyManager;
import com.benshou.bcss.common.trace.TraceIdContext;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * HTTP 请求过滤器
 *
 * @author <a href="mailto:liuzhenbang@benshou.com">Liu Zhenbang</a>
 */
@Component
@Slf4j
public class RequestFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(
            @NotNull HttpServletRequest request,
            @NotNull HttpServletResponse response,
            @NotNull FilterChain filterChain) throws ServletException, IOException {
        // 日志打印
        try {
            if (logger.isDebugEnabled()) {
                logger.debug("""
                    \n-----------------------------开始请求-----------------------------
                    TraceId   : {}
                    URL       : {}
                    Headers   : \\{{}\\}
                    ----------------------------------------------------------------
                    """.stripIndent(),
                        TraceIdContext.get(),
                        buildURLLog(request),
                        buildHeaderLog(request));
            }
        } catch (Exception e) {
            logger.warn("Exception: ", e);
        }

        try {
            // 将需要的 Request Header 设置到上下文环境中
            new _HeaderManager(request, response)
                    .manage(
                            "X-Tenant-Id",
                            null,
                            Long::parseLong,
                            RequestHeaderContext.TENANT_ID)
                    .manage(
                            "X-User-No",
                            null,
                            null,
                            RequestHeaderContext.USER_NO)
                    .manage(
                            "X-Timezone",
                            () -> List.of(ZoneId.systemDefault().getId()),
                            ZoneId::of,
                            RequestHeaderContext.TIMEZONE);
            filterChain.doFilter(request, response);
        } finally {
            // 清除上下文
            RequestHeaderContext.clearAll();
            // 打印日志
            try {
                if (logger.isDebugEnabled()) {
                    logger.debug("""
                    \n-----------------------------结束请求-----------------------------
                    TraceId   : {}
                    URL       : {}
                    Headers   : \\{{}\\}
                    ----------------------------------------------------------------
                    """.stripIndent(),
                            TraceIdContext.get(),
                            buildURLLog(request),
                            buildHeaderLog(response));
                }
            } catch (Exception e) {
                logger.warn("Exception: ", e);
            }
        }
    }

    /**
     * Request Header 管理器
     *
     * @param request HTTP 请求对象
     * @param response HTTP 响应对象
     */
    private record _HeaderManager(
            @NotNull HttpServletRequest request,
            @NotNull HttpServletResponse response) {

        /**
         * 应用于对应名称的 Header
         *
         * @param headerName Header 的名称
         * @param defaultPropertySupplier 默认值提供方法
         * @param propertyMapper 字段属性转化方法
         * @param propertyManager 字段属性管理器
         * @return 自身
         */
        <U> _HeaderManager manage(
                @NotNull String headerName,
                Supplier<@NotNull List<String>> defaultPropertySupplier,
                Function<String, U> propertyMapper,
                @NotNull PropertyManager<List<U>> propertyManager) {
            // 获取对应的所有 Header 值
            var headers = this.request.getHeaders(headerName);
            List<String> values = new ArrayList<>();
            while (headers.hasMoreElements()) {
                var value = headers.nextElement();
                values.add(value);
            }

            // 如无则取默认值
            if (values.isEmpty() && defaultPropertySupplier != null) {
                values = defaultPropertySupplier.get();
            }

            // 在响应中也设置，用于声明服务处理环境
            values.forEach(value -> this.response.addHeader(headerName, value));
            @SuppressWarnings("unchecked")
            var property = values.isEmpty() ?
                    null :
                    propertyMapper == null ?
                            (List<U>) values :
                            values.stream()
                                    .map(propertyMapper)
                                    .toList();
            propertyManager.set(property);

            return this;
        }
    }

    /**
     * 构建 Header 的日志
     *
     * @param request HTTP 请求对象
     * @return 日志
     */
    private static String buildHeaderLog(@NotNull HttpServletRequest request) {
        var logBuilder = new StringBuilder();
        var names = request.getHeaderNames();
        while (names.hasMoreElements()) {
            var name = names.nextElement();
            var values = request.getHeaders(name);
            while (values.hasMoreElements()) {
                appendHeaderLog(logBuilder, name, values.nextElement());
            }
        }

        return logBuilder.toString();
    }

    /**
     * 构建 Header 的日志
     *
     * @param response HTTP 响应对象
     * @return 日志
     */
    private static String buildHeaderLog(@NotNull HttpServletResponse response) {
        var logBuilder = new StringBuilder();
        for (var name : response.getHeaderNames()) {
            for (var value : response.getHeaders(name)) {
                appendHeaderLog(logBuilder, name, value);
            }
        }

        return logBuilder.toString();
    }

    /**
     * 追加 Header 的日志
     *
     * @param logBuilder 日志构建器
     * @param headerName Header 名称
     * @param headerValue Header 值
     */
    private static void appendHeaderLog(
            @NotNull StringBuilder logBuilder,
            @NotNull String headerName,
            @NotNull String headerValue) {
        logBuilder.append("\n    ").append(headerName).append(": ").append(headerValue);
    }

    /**
     * 构建请求 URL 的日志
     *
     * @param request HTTP 请求对象
     * @return 日志
     */
    private static String buildURLLog(@NotNull HttpServletRequest request) {
        return request.getMethod() + " " +
                request.getScheme() + " " +
                request.getRequestURI() + Optional.ofNullable(request.getQueryString())
                .map(query -> "?" + query)
                .orElse("");
    }
}
